(function(exports) {
    //Cell--------------------------------------------------------------
    function Cell(x, y) {
        this.x = x;
        this.y = y;
        this.occupant = undefined;
    }

    //Food--------------------------------------------------------------
    //Food is not explicitly deleted, here's hoping the garbage collector
    //is working.
    function Food(x, y, snakeBoard) {
        this.type = 'food';
        this.x = x;
        this.y = y;
        this.colour = 'black';
        this.snakeBoard = snakeBoard;
        this.snakeBoard.cells[this.x][this.y].occupant = this;
    }

    //Snake board-------------------------------------------------------

    function SnakeBoard(width, height) {
        this.width = width;
        this.height = height;
        this.cells = [];
        for (var x = 0; x < this.width; x++) {
            this.cells.push([]);
            for (var y = 0; y < this.height; y++) {
                this.cells[x].push(new Cell(x, y))
            }
        }
    }

    SnakeBoard.prototype.clear = function (context) {
        for (var x = 0; x < this.width; x++) {
            for (var y = 0; y < this.height; y++) {
                this.cells[x][y].occupant = undefined;
            }
        }
    };

    SnakeBoard.prototype.render = function (context) {
        var cellSizeX = 1.0 / this.width;
        var cellSizeY = 1.0 / this.height;
        for (var x = 0; x < this.width; x++) {
            for (var y = 0; y < this.height; y++) {

                if (this.cells[x][y].occupant) {
                    context.beginPath();
                    context.rect(x * cellSizeX, y * cellSizeY, cellSizeX, cellSizeY);
                    context.fillStyle = this.cells[x][y].occupant.colour;
                    context.fill();
                }
            }
        }
    };

    //Snake-------------------------------------------------------------

    function Snake(name, snakeBoard, colour, segments, direction, eat, lose) {
        this.type = 'snake';
        this.name = name;
        this.snakeBoard = snakeBoard;
        this.colour = colour;
        this.length = segments.length;
        this.direction = direction;
        this.segments = segments;
        this.lastDirection = undefined;

        //functions for events
        this.eat = eat;
        this.lose = lose;

        //Claim ownership of board cells
        for (var segID = 0; segID < this.segments.length; segID++) {
            var seg = this.segments[segID];
            this.snakeBoard.cells[seg.x][seg.y].occupant = this;
        }
    }

    Snake.directions = {
        UP: 0,
        RIGHT: 1,
        DOWN: 2,
        LEFT: 3
    };

    Snake.prototype.nextBlock = function () {
        var seg = {
            x: this.segments[0].x,
            y: this.segments[0].y
        };
        switch (this.direction) {
            case Snake.directions.UP:
                seg.y = (this.segments[0].y - 1) % this.snakeBoard.height;
                if (seg.y < 0) {
                    seg.y += this.snakeBoard.height;
                }
                break;
            case Snake.directions.RIGHT:
                seg.x = (this.segments[0].x + 1) % this.snakeBoard.width;
                break;
            case Snake.directions.DOWN:
                seg.y = (this.segments[0].y + 1) % this.snakeBoard.height;
                break;
            case Snake.directions.LEFT:
                seg.x = (this.segments[0].x - 1) % this.snakeBoard.width;
                if (seg.x < 0) {
                    seg.x += this.snakeBoard.width;
                }
                break;
        }
        return seg;
    };

    Snake.prototype.changeDirection = function (direction) {
        if (direction == Snake.directions.UP && this.lastDirection != Snake.directions.DOWN) {
            this.direction = direction;
        }
        else if (direction == Snake.directions.RIGHT && this.lastDirection != Snake.directions.LEFT) {
            this.direction = direction;
        }
        else if (direction == Snake.directions.DOWN && this.lastDirection != Snake.directions.UP) {
            this.direction = direction;
        }
        else if (direction == Snake.directions.LEFT && this.lastDirection != Snake.directions.RIGHT) {
            this.direction = direction;
        }

    };

    Snake.prototype.tick = function () {

        var nextSeg = this.nextBlock();
        var lastSeg = this.segments[this.segments.length - 1];
        var nextCell = this.snakeBoard.cells[nextSeg.x][nextSeg.y];
        if (nextCell.occupant) {
            switch (nextCell.occupant.type) {
                case('snake'):
                    if (this.lose) {
                        this.lose(this.name)
                    }

                    break;
                case('food'):
                    //add a segment to the end of the snake
                    if (this.eat) {
                        this.eat()
                    }

                    this.length += 1;
                    this.segments.push({x: lastSeg.x, y: lastSeg.y});
                    break;
            }
        }

        var seg;
        //update snake segments from tail to head-1
        for (var segID = this.segments.length - 1; segID > 0; segID--) {
            seg = this.segments[segID];
            if (segID == this.segments.length - 1) {
                this.snakeBoard.cells[seg.x][seg.y].occupant = undefined;
            }
            seg.x = this.segments[segID - 1].x;
            seg.y = this.segments[segID - 1].y;

        }

        //update head
        this.segments[0] = nextSeg;
        this.snakeBoard.cells[this.segments[0].x][this.segments[0].y].occupant = this;

        this.lastDirection = this.direction;
    };

    //Used by server/testing to generate snake locations
    Snake.generateSnakeSegments = function (startx, starty, len) {
        var segments = [];
        for (var y = starty; y < (starty + len); y++) {
            segments.push({x: startx, y: y});
        }
        return segments;
    };

    exports.Snake = Snake;
    exports.Cell = Cell;
    exports.Food = Food;
    exports.SnakeBoard = SnakeBoard;

})(typeof exports === 'undefined'? this['Snake']={}: exports);